package business;

import org.apache.log4j.Logger;

import java.util.concurrent.TimeUnit;

/**
 * Created by aleks_000 on 10.12.13.
 */
// todo: add a user to operation (to hold money, look at the task)
public class ReplaceMoneyOperation implements BankOperation {
    // logger no account format string
    private static final String NO_ACCOUNT_STRING_FORMAT = "There is no account with id %d!";
    // successful operation
    private static final String TRANSFER_SUCCESSFUL = "Transfer successfully finished from account %d to account %d (transfer value: %f)";
    // source account state value
    private static final String SOURCE_ACCOUNT_STATE_FORMAT = "Source account %d state: %f";
    // destination
    private static final String DESTINATION_ACCOUNT_STATE_FORMAT = "Destination account %d state: %f";


    // represent a number of account from money will be transfered
    private final int fromAccountKey;
    // represent a number of account money will be transfered to
    private final int toAccountKey;
    // represent amount of money to transfered
    private final double moneyToTransfer;
    // represent a bank to operation
    private final Bank bank;
    // logger
    private final static Logger LOGGER = Logger.getLogger(ReplaceMoneyOperation.class);


    public int getFromAccountKey() {
        return fromAccountKey;
    }

    public int getToAccountKey() {
        return toAccountKey;
    }

    public double getValue() {
        return moneyToTransfer;
    }

    public Bank getBank() {
        return bank;
    }

    public ReplaceMoneyOperation(int fromAccountKey, int toAccountKey, double moneyToTransfer, Bank bank) {
        if (bank == null) {
            throw new IllegalArgumentException();
        }
        this.bank = bank;
        this.fromAccountKey = fromAccountKey;
        this.toAccountKey = toAccountKey;
        this.moneyToTransfer = moneyToTransfer;
    }

    @Override
    public void run() {
        double mementoFrom = 0, mementoTo = 0;
        boolean isMementoFromAvailable = false, isMementoToAvailable = false;
        // get accounts
        if (!bank.hasAccount(fromAccountKey)) {
            LOGGER.warn(String.format(NO_ACCOUNT_STRING_FORMAT, fromAccountKey));
            return;
        }
        Account fromAccount = bank.getAccount(fromAccountKey);
        if (!bank.hasAccount(toAccountKey)) {
            LOGGER.warn(String.format(NO_ACCOUNT_STRING_FORMAT, toAccountKey));
            return;
        }
        Account toAccount = bank.getAccount(toAccountKey);

        // perform transfer
        try {
            // decrease one account
            synchronized (fromAccount) {
                mementoFrom = fromAccount.getValue();
                isMementoFromAvailable = true;
                fromAccount.setBlocked(true);
                fromAccount.decrease(moneyToTransfer);
                TimeUnit.SECONDS.sleep(2);
                fromAccount.setBlocked(false);
            }
            TimeUnit.SECONDS.sleep(3);

            // increase the second account
            synchronized (toAccount) {
                mementoTo = toAccount.getValue();
                isMementoToAvailable = true;
                toAccount.setBlocked(true);
                toAccount.increase(moneyToTransfer);
                TimeUnit.SECONDS.sleep(2);
                toAccount.setBlocked(false);
            }

            TimeUnit.SECONDS.sleep(3);

            synchronized (LOGGER) {
                LOGGER.info(String.format(TRANSFER_SUCCESSFUL, fromAccountKey, toAccountKey, moneyToTransfer));
                // todo: think about blocking
                LOGGER.info(String.format(SOURCE_ACCOUNT_STATE_FORMAT, fromAccountKey, fromAccount.getValue()));
                LOGGER.info(String.format(DESTINATION_ACCOUNT_STATE_FORMAT, toAccountKey, toAccount.getValue()));
            }
        } catch (Exception ex) {
            synchronized (LOGGER) {
                LOGGER.error(ex.getMessage());
                LOGGER.trace(ex.getMessage(), ex);
            }
            ex.printStackTrace();
            // restore money
            synchronized (fromAccount) {
                if (isMementoFromAvailable) {
                    fromAccount.setMoney(mementoFrom);
                }
                fromAccount.setBlocked(false);
            }
            synchronized (toAccount) {
                if (isMementoToAvailable) {
                    toAccount.setMoney(mementoTo);
                }
                toAccount.setBlocked(false);
            }
        }
    }
}
